package org.example.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.example.dto.ExportTask;
import org.example.entity.User;
import org.example.service.UserService;
import org.example.util.ExcelUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;

@RestController
@RequestMapping("/exportThread")
public class ExportWithThreadController {

    @Autowired
    private UserService userService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final ObjectMapper objectMapper = new ObjectMapper();


    // 每批处理的数据量（根据内存调整，建议1万-5万条）
    private static final int BATCH_SIZE = 10000;

    // 线程池配置（核心线程数=CPU核心数，避免资源耗尽）
    private final ExecutorService executor = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors() + 1,
            60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),
            new ThreadPoolExecutor.CallerRunsPolicy() // 任务满时让提交者线程执行，避免任务丢失
    );


    // 2. 查询导出进度
    @GetMapping("/export/progress")
    public ExportTask getProgress(@RequestParam String taskId) {
        ExportTask task = getTask(taskId);
        return task;
    }

    // 3. 下载导出文件（任务完成后调用）
    @GetMapping("/export/download")
    public void downloadFile(@RequestParam String taskId, HttpServletResponse response) throws IOException {
        ExportTask task = getTask(taskId);
        if (task == null || !"COMPLETE".equals(task.getStatus())) {
            response.sendError(400, "任务未完成或不存在");
            return;
        }
        // 从临时路径读取文件并下载（实际项目中建议用Nginx托管静态文件）
        ExcelUtil.download(response, task.getFileUrl(), "用户列表.xlsx");
        System.out.println("文件下载完成===>");
        printCostTime();
    }

    @GetMapping("/export/start")
    public String startExport() throws JsonProcessingException {

        redisTemplate.opsForValue().set("start_time", System.currentTimeMillis() + "");

        String taskId = "export_" + UUID.randomUUID().toString().replace("-", "");
        ExportTask task = new ExportTask();
        task.setTaskId(taskId);
        task.setProgress(0);
        task.setStatus("PROCESSING");
        task.setCreateTime(new Date());
        writeTask(taskId, task);

        // 异步执行导出
        String tempFilePath = "/tmp/export_" + taskId + ".xlsx"; // 临时文件路径
        executor.submit(() -> exportToExcel(taskId, tempFilePath));
        return taskId;
    }

    /**
     * 导出百万级数据到Excel
     * EasyExcel的模板引擎本身并不直接支持多线程写入同一个Excel文件
     */
    public void exportToExcel(String taskId, String tempFilePath) {
        // 1. 查询总条数，计算分片数
        int total = userService.count(); // SELECT COUNT(*) FROM 表名
        int totalPages = (total + BATCH_SIZE - 1) / BATCH_SIZE; // 向上取整
        redisTemplate.opsForValue().set("total_" + taskId, Integer.toString(total));

        // 2. 多线程并行查询各分片数据，结果暂存到线程安全的集合
        List<Future<List<User>>> futures = new ArrayList<>();
//        for (int pageNum = 0; pageNum < totalPages; pageNum++) {
//            int currentPage = pageNum;
//            // 提交分页查询任务
//
//            System.out.println(currentPage + ", "+ BATCH_SIZE);
//            Future<List<User>> future = executor.submit(() ->
//                    userService.getUsersByPage(currentPage, BATCH_SIZE)
//            );
//            futures.add(future);
//        }

        for (int pageNum = 0; pageNum < totalPages; pageNum++) {

            long lastId = pageNum * BATCH_SIZE;
            System.out.println(lastId + ", " + BATCH_SIZE);
            Future<List<User>> future = executor.submit(() ->
                // 使用深分页优化
                userService.findListByLastId(lastId, BATCH_SIZE)
            );
            futures.add(future);
        }


//        try {
//            Thread.sleep(10000L);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        // 3. 收集所有分片数据，写入Excel（单线程汇总或多Sheet并发）
        writeExcel(futures, tempFilePath);

        // 更新状态
        ExportTask task = getTask(taskId);
        task.setProgress(100);
        task.setStatus("COMPLETE");
        task.setFileUrl(tempFilePath);
        writeTask(taskId, task);

        // 4. 关闭线程池
//        executor.shutdown();
    }

    // 统计 futures 中所有 User 的总数量
    public int countUsersSnapshot(List<Future<List<User>>> futures) {
        int sum = 0;
        for (Future<List<User>> future : futures) {
            try {
                // 获取当前 Future 的结果（阻塞直到该线程任务完成）
                List<User> userList = future.get();
                // 累加当前列表的 User 数量
                sum += userList.size();

            } catch (InterruptedException e) {
                // 线程被中断时的处理（如恢复中断状态）
                Thread.currentThread().interrupt();
                System.err.println("线程被中断：" + e.getMessage());
                break; // 或根据需求处理
            } catch (ExecutionException e) {
                // 任务执行异常（如查询数据库失败）
                System.err.println("任务执行失败：" + e.getCause().getMessage());
                // 可选择继续统计其他任务，或抛出异常终止
            }
        }
        return sum;
    }


    /**
     * 方案一：多线程写多 Sheet（适合超大数据）
     *
     * @param
     * @return
     */

    private void writeExcel(List<Future<List<User>>> futures, String filePath) {
        // 初始化Excel写入器（指定文件路径和数据模型）
        System.out.println("futures的size：" + futures.size());
        int userSum = countUsersSnapshot(futures);
        System.out.println("数据库数据读取完毕," + userSum);
        //26秒 -> 8秒
        printCostTime();
        ExcelWriter excelWriter = EasyExcel.write(filePath, User.class).build();
        try {
            // 遍历所有分片结果，每个分片写入一个Sheet
            for (int i = 0; i < futures.size(); i++) {
                List<User> dataList = futures.get(i).get(); // 获取线程查询结果
                if (CollectionUtils.isEmpty(dataList)) continue;

                // 每个Sheet命名为"数据-分片X"
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "数据-分片" + (i + 1)).build();
                excelWriter.write(dataList, writeSheet); // 写入当前Sheet
            }
        } catch (Exception e) {
            System.out.println("写入Excel失败");
            e.printStackTrace();
        } finally {
            // 关闭写入器，释放资源
            if (excelWriter != null) {
                excelWriter.finish();
            }
            System.out.println("临时文件写入结束");
            printCostTime();
        }
    }


    /**
     * 方案二：单线程汇总写入（适合数据量中等，格式统一）
     *
     * @param
     * @return
     */
    private void writeExcel2(List<Future<List<User>>> futures, String filePath) {
        List<User> allData = new CopyOnWriteArrayList<>(); // 线程安全的集合

        // 收集所有分片数据
        for (Future<List<User>> future : futures) {
            try {
                allData.addAll(future.get());
            } catch (Exception e) {
                System.out.println("获取分片数据失败");
            }
        }

        // 单线程写入Excel（整个数据写入一个Sheet）
        EasyExcel.write(filePath, User.class)
                .sheet("全部数据")
                .doWrite(allData);
    }


    private ExportTask getTask(String taskId) {
        String taskStr = redisTemplate.opsForValue().get(taskId);
        ExportTask task = null;
        try {
            task = objectMapper.readValue(taskStr, ExportTask.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return task;
    }

    private void writeTask(String taskId, ExportTask task) {
        String taskStr = null;
        try {
            taskStr = objectMapper.writeValueAsString(task);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        redisTemplate.opsForValue().set(taskId, taskStr);
    }

    private void printCostTime() {
        String startTime = redisTemplate.opsForValue().get("start_time");
        long l = System.currentTimeMillis() - Long.parseLong(startTime);
        System.out.println("耗时：" + l / 1000 + "秒");
    }

}